#include <stdio.h>

#ifdef OPENAL
#include <AL/AL.h>
#include <AL/ALC.h>
#endif // #ifdef OPENAL
#include <SDL.h>
#include <modplug.h>

#include "audio.h"


#define BUF_SIZE 65536 // in bytes
#define BUF_COUNT 4
#define USE_TIMER // instead of thread


static volatile bool initialized = false;
static ModPlugFile *mpFile = 0;
#ifdef OPENAL
static ALuint buffers[2];
static ALuint streamSource;
static SDL_Thread *thread = 0;
static SDL_TimerID timer;
static volatile bool streaming = false;
#endif // #ifdef OPENAL


#ifdef OPENAL
static void streamProc(ALuint buf)
{
  char data[BUF_SIZE];
  //printf("Fill\n");
  ModPlug_Read(mpFile, data, BUF_SIZE);
  //fwrite(data, 1, BUF_SIZE, output);

  alBufferData(buf, AL_FORMAT_STEREO16, data, BUF_SIZE, 44100);
}

#ifndef USE_TIMER
static int streamCallback(void *user)
{
  streamProc(buffers[0]);
  streamProc(buffers[1]);
  alSourceQueueBuffers(streamSource, 2, buffers);
  alSourcePlay(streamSource);
  //stream_t *data = static_cast<stream_t*>(user);
  while(streaming)
  {
    ALint proc;
    //ALint num;
    ALuint buf;
    alGetSourcei(streamSource, AL_BUFFERS_PROCESSED, &proc);
    //alGetSourcei(streamSource, AL_BUFFERS_QUEUED, &num);
    while(proc--)
    {
      alSourceUnqueueBuffers(streamSource, 1, &buf);
      streamProc(buf);
      alSourceQueueBuffers(streamSource, 1, &buf);
      //SDL_Delay(1000*44100/(BUF_SIZE*4)); // best sleep time
    }
    //else SDL_Delay(1);
    SDL_Delay(10);

    int state;
    alGetSourcei(streamSource, AL_SOURCE_STATE, &state);
    // has stream been interrupted?
    if(streaming&&state!=AL_PLAYING)
    {
      alSourcePlay(streamSource);
    }
  }
  return 0;
}
#else
static Uint32 timerCallback(Uint32 interval, void *param)
{
  ALint proc;
  //ALint num;
  ALuint buf;
  alGetSourcei(streamSource, AL_BUFFERS_PROCESSED, &proc);
  //alGetSourcei(streamSource, AL_BUFFERS_QUEUED, &num);
  //printf("Processed: %d/%d\n", proc, num);
  while(proc--)
  {
    alSourceUnqueueBuffers(streamSource, 1, &buf);
    streamProc(buf);
    alSourceQueueBuffers(streamSource, 1, &buf);
  }

  int state;
  alGetSourcei(streamSource, AL_SOURCE_STATE, &state);
  // has stream been interrupted?
  if(streaming&&state!=AL_PLAYING)
  {
    alSourcePlay(streamSource);
  }

  return interval;
}
#endif // #ifdef USE_TIMER
#else
void streamProc(void *user, Uint8 *stream, int len)
{
  ModPlug_Read(mpFile, stream, len);
}
#endif // #ifdef OPENAL

int audio::init()
{
#ifdef OPENAL
  ALCdevice *dev;
  ALCcontext *context;

  dev = alcOpenDevice(0); // select the "preferred" device
  if(!dev) return 1;

  context = alcCreateContext(dev, 0);
  if(!context) return 2;
  alcMakeContextCurrent(context);

  alGenBuffers(2, buffers);
#ifdef USE_TIMER
  SDL_InitSubSystem(SDL_INIT_TIMER);
#endif // #ifdef USE_TIMER
#else
  SDL_InitSubSystem(SDL_INIT_AUDIO);

  SDL_AudioSpec spec;
  spec.freq = 44100;
  spec.format = AUDIO_S16;
  spec.channels = 2;
  spec.samples = 1024;
  spec.callback = streamProc;
  spec.userdata = 0;

  if(SDL_OpenAudio(&spec, 0)<0) return 1;
#endif // #ifdef OPENAL

  initialized = true;

  return 0;
}

void audio::quit()
{
  stopMusic();
#ifdef OPENAL
  ALCdevice *dev;
  ALCcontext *context;
  initialized = false;

  alDeleteBuffers(2, buffers);
#else
  SDL_CloseAudio();
#endif // #ifdef OPENAL

#ifdef OPENAL
  context = alcGetCurrentContext();
  dev = alcGetContextsDevice(context);
  alcMakeContextCurrent(0);
  alcDestroyContext(context);
  alcCloseDevice(dev);
#ifdef USE_TIMER
  SDL_QuitSubSystem(SDL_INIT_TIMER);
#endif // #ifdef USE_TIMER
#else
  SDL_QuitSubSystem(SDL_INIT_AUDIO);
#endif // #ifdef OPENAL
}

/* const char *audio::getErrorString()
{
  switch(alGetError())
  {
    case AL_NO_ERROR: return "None"; break;
    case AL_INVALID_NAME: return "Invalid Name"; break;
    case AL_INVALID_ENUM: return "Invalid Enum"; break;
    case AL_INVALID_VALUE: return "Invalid Value"; break;
    case AL_INVALID_OPERATION: return "Invalid Operation"; break;
    case AL_OUT_OF_MEMORY: return "Out Of Memory"; break;
    default: return "Unknown";
  }
} */


void audio::openMusic(const char *fname)
{
  // don't do anything if we are not able to play something :P
  if(!initialized)
  {
    printf("Not initialized!\n");
    return;
  }

  stopMusic();

  // load file into memory
  FILE *fp = fopen((std::string("data/")+fname).c_str(), "rb"); // open file
  fseek(fp, 0, SEEK_END); // get file size
  int size = ftell(fp);
  fseek(fp, 0, SEEK_SET); // reset file position
  char *buf = new char[size]; // read contents
  fread(buf, size, 1, fp);
  fclose(fp); // close file

  mpFile = ModPlug_Load(buf, size);
  delete[] buf; // file no longer needed in memory

  if(!mpFile)
  {
    printf("Couldn't open mod file!\n");
    return;
  }

  ModPlug_Settings settings;
  ModPlug_GetSettings(&settings);
  settings.mLoopCount = -1; // loop forever
  ModPlug_SetSettings(&settings);
}

void audio::playMusic()
{
  if(!mpFile) return;
#ifdef OPENAL
  alGenSources(1, &streamSource);
  streaming = true;
#ifndef USE_TIMER
  thread = SDL_CreateThread(&streamCallback, 0);
#else
  streamProc(buffers[0]);
  streamProc(buffers[1]);
  alSourceQueueBuffers(streamSource, 2, buffers);
  alSourcePlay(streamSource);
  Uint32 interval = 1000*44100/(BUF_SIZE*4);
  timer = SDL_AddTimer(interval, timerCallback, 0);
#endif // #ifndef USE_TIMER
#else
  SDL_PauseAudio(0);
#endif // #ifdef OPENAL
}

void audio::stopMusic()
{
#ifdef OPENAL
  if(streaming)
  {
    streaming = false;
    alSourceStop(streamSource);
#ifndef USE_TIMER
    if(thread)
    {
      SDL_WaitThread(thread, 0);
      thread = 0;
    }
#else
    SDL_RemoveTimer(timer);
#endif // #ifndef USE_TIMER
    alDeleteSources(1, &streamSource);
  }
#else
  SDL_PauseAudio(1);
#endif // #ifdef OPENAL

  if(mpFile) ModPlug_Unload(mpFile);
}
